{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import panel as pn\n",
    "pn.extension('echarts')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ``ECharts`` pane renders [Apache ECharts](https://echarts.apache.org/en/index.html) and [pyecharts](https://pyecharts.org/#/) plots inside Panel. Note that to use the ``ECharts`` pane in the notebook the Panel extension has to be loaded with 'echarts' as an argument to ensure that echarts.js is initialized. \n",
    "\n",
    "#### Parameters:\n",
    "\n",
    "For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n",
    "\n",
    "* **``object``** (dict): An ECharts plot specification expressed as a Python dictionary, which is then converted to JSON. Or a pyecharts chart like `pyecharts.charts.Bar`.\n",
    "* **``options``** (dict): An optional dict of options passed to [`Echarts.setOption`](https://echarts.apache.org/en/api.html#echartsInstance.setOption). Allows to fine-tune the rendering behavior. For example, you might want to use `options={ \"replaceMerge\": ['series'] })` when updating the `objects` with a value containing a smaller number of series. \n",
    "* **``renderer``** (str): Whether to render with HTML 'canvas' (default) or 'svg'\n",
    "* **``theme``** (str): Theme to apply to plots (one of 'default', 'dark', 'light')\n",
    "___"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lets try the ``ECharts`` pane support for ECharts specs in its raw form (i.e. a dictionary), e.g. here we declare a bar plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "echart_bar = {\n",
    "    'title': {\n",
    "        'text': 'ECharts entry example'\n",
    "    },\n",
    "    'tooltip': {},\n",
    "    'legend': {\n",
    "        'data':['Sales']\n",
    "    },\n",
    "    'xAxis': {\n",
    "        'data': [\"shirt\",\"cardign\",\"chiffon shirt\",\"pants\",\"heels\",\"socks\"]\n",
    "    },\n",
    "    'yAxis': {},\n",
    "    'series': [{\n",
    "        'name': 'Sales',\n",
    "        'type': 'bar',\n",
    "        'data': [5, 20, 36, 10, 10, 20]\n",
    "    }],\n",
    "};\n",
    "echart_pane = pn.pane.ECharts(echart_bar, height=480, width=640)\n",
    "echart_pane"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Like all other panes, the ``ECharts`` pane ``object`` can be updated, either in place and triggering an update:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "echart_bar['series'] = [dict(echart_bar['series'][0], type='line')]\n",
    "echart_pane.param.trigger('object')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vega specification can also be responsively sized by declaring the width or height to match the container:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "responsive_spec = dict(echart_bar, responsive=True)\n",
    "\n",
    "pn.pane.ECharts(responsive_spec, height=400)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ECharts pane also has support for pyecharts. For example, we can pass a `pyecharts.charts.Bar` chart directly the `ECharts` pane."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from pyecharts.charts import Bar\n",
    "\n",
    "bar1 = pn.widgets.IntSlider(start=1, end=100, value=50)\n",
    "bar2 = pn.widgets.IntSlider(start=1, end=100, value=50)\n",
    "\n",
    "def plot(bar1, bar2):\n",
    "    my_plot= (Bar()\n",
    "        .add_xaxis(['Helicoptors', 'Planes'])\n",
    "        .add_yaxis('Total In Flight', [bar1, bar2])\n",
    "    )\n",
    "    return pn.pane.ECharts(my_plot, width=500, height=250)\n",
    "pn.Row(\n",
    "    pn.Column(bar1, bar2),\n",
    "    pn.bind(plot, bar1, bar2),\n",
    ").servable()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The ECharts library supports a wide range of chart types and since the plots are expressed using JSON datastructures we can easily update the data and then emit change events to update the charts:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "gauge = {\n",
    "    'tooltip': {\n",
    "        'formatter': '{a} <br/>{b} : {c}%'\n",
    "    },\n",
    "    'series': [\n",
    "        {\n",
    "            'name': 'Gauge',\n",
    "            'type': 'gauge',\n",
    "            'detail': {'formatter': '{value}%'},\n",
    "            'data': [{'value': 50, 'name': 'Value'}]\n",
    "        }\n",
    "    ]\n",
    "};\n",
    "gauge_pane = pn.pane.ECharts(gauge, width=400, height=400)\n",
    "\n",
    "slider = pn.widgets.IntSlider(value=50, start=0, end=100)\n",
    "\n",
    "slider.jscallback(args={'gauge': gauge_pane}, value=\"\"\"\n",
    "gauge.data.series[0].data[0].value = cb_obj.value\n",
    "gauge.properties.data.change.emit()\n",
    "\"\"\")\n",
    "\n",
    "pn.Column(slider, gauge_pane)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Events\n",
    "\n",
    "The `EChart` object allows you to listen to any event defined in the Javascript API, either by listening to the event in Python using the `on_event` method or by triggering a Javascript callback with the `js_on_event` method.\n",
    "\n",
    "For details on what events you can [ECharts events documentation](https://echarts.apache.org/handbook/en/concepts/event).\n",
    "\n",
    "### Python\n",
    "\n",
    "Let us start with a simple click event we want to listen to from Python. To add an event listener we simple call the `on_event` method with the event type (in this case 'click') and our Python handler."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "echart_pane = pn.pane.ECharts(echart_bar, height=480, width=640)\n",
    "json = pn.pane.JSON()\n",
    "\n",
    "def callback(event):\n",
    "    json.object = event.data\n",
    "\n",
    "echart_pane.on_event('click', callback)\n",
    "\n",
    "pn.Row(echart_pane, json)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Try clicking on a point on the line. When inspecting the `json.object` after a click you should see something like this:\n",
    "\n",
    "```python\n",
    "{'componentType': 'series',\n",
    " 'componentSubType': 'line',\n",
    " 'componentIndex': 0,\n",
    " 'seriesType': 'line',\n",
    " 'seriesIndex': 0,\n",
    " 'seriesId': '\\x00Sales\\x000',\n",
    " 'seriesName': 'Sales',\n",
    " 'name': 'shirt',\n",
    " 'dataIndex': 0,\n",
    " 'data': 5,\n",
    " 'value': 5,\n",
    " 'color': '#5470c6',\n",
    " 'dimensionNames': ['x', 'y'],\n",
    " 'encode': {'x': [0], 'y': [1]},\n",
    " '$vars': ['seriesName', 'name', 'value'],\n",
    " 'event': {'detail': 1,\n",
    "  'altKey': False,\n",
    "  'button': 0,\n",
    "  'buttons': 0,\n",
    "  'clientX': 507,\n",
    "  'clientY': 911,\n",
    "  'ctrlKey': False,\n",
    "  'metaKey': False,\n",
    "  'pageX': 507,\n",
    "  'pageY': 911,\n",
    "  'screenX': 3739,\n",
    "  'screenY': 762,\n",
    "  'shiftKey': False,\n",
    "  'target': {'boundingClientRect': {}},\n",
    "  'currentTarget': {'boundingClientRect': {}},\n",
    "  'relatedTarget': None},\n",
    " 'type': 'click'}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To restrict what types of objects a particular event applies to you can also provide a `query` argument to the `on_event` method. The format of the `query` should be `mainType` or `mainType.subType`, such as:\n",
    "\n",
    "- `'series'`: Fire event when clicking on data series\n",
    "- `'series.line'`: Fire event only when clicking on a line data series.\n",
    "- `'dataZoom'`: Fire event when clicking on zoom.\n",
    "- `'xAxis.category'`: Fire event when clicking on a category on the xaxis."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Javascript\n",
    "\n",
    "The same concepts apply in Javascript, however here we pass in Javascript code a JS snippet. The namespace allows you to access the event data `cb_data` and the ECharts chart itself as `cb_obj`. In this way you have access to the event and can manipulate the plot yourself:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "echart_pane = pn.pane.ECharts(echart_bar, height=480, width=640)\n",
    "\n",
    "echart_pane.js_on_event('click', 'alert(`Clicked on point: ${cb_data.dataIndex}`)')\n",
    "\n",
    "echart_pane"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to modify another object in response to an event triggered on the chart you can pass additional objects to the `json_on_event` method. The corresponding Bokeh model will then be made available in the callback. As an example here we make the `JSON` pane available so that we can update it on a click event:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "echart_pane = pn.pane.ECharts(echart_bar, height=480, width=640)\n",
    "json = pn.pane.JSON()\n",
    "\n",
    "echart_pane.js_on_event('click', \"\"\"\n",
    "event = {...cb_data}\n",
    "delete event.event\n",
    "json.text = JSON.stringify(event)\n",
    "\"\"\", json=json)\n",
    "\n",
    "pn.Row(echart_pane, json)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Controls\n",
    "\n",
    "The `EChart` pane exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pn.Row(gauge_pane.controls(jslink=True), gauge_pane)"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
